Day 68 - Authentication & Flask Login & Werkzeug


Posted by pei_______ on 2022-06-23

learning from 100 Days of Code: The Complete Python Pro Bootcamp for 2022


Flask Login - Documentation
Werkzeug - Documentation


Learning Point - Flask & Flask login


Point 1. Flask - return / download files

from flask import send_from_directory

@app.route('/download')
def download():
    filename = request.args.get('filename')
    return send_from_directory('static/files/',
                               filename, as_attachment=True)

Point 2. Multiple inheritance and mixin classes in Python

Point 3. Login required

Point 4. Flashing Message

Point 5. Template Inheritance

notes in project


Learning Point - Werkzeug


Point 1. Hash & Salt the password

from werkzeug.security import generate_password_hash

hash_and_salted_psw = generate_password_hash(
    request.form['password'], 
    method='pbkdf2:sha256', 
    salt_length=8
    )

> method$salt$hash

Point 2. Check the password

from werkzeug.security import check_password_hash

password = request.form['password']
check_password_hash(% HASH PASSWORD %, password)
> True / False

Authentication Project


main.py

from flask import Flask, render_template, request, url_for, redirect, flash, send_from_directory
from werkzeug.security import generate_password_hash, check_password_hash
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin, login_user, LoginManager, login_required, current_user, logout_user

app = Flask(__name__)

app.config['SECRET_KEY'] = 'any-secret-key-you-choose'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

# ---------- 01. set the login manager in whole app---------- #

login_manager = LoginManager()
login_manager.init_app(app)


# ---------- 02. set the user class with UserMixin ---------- #

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(100), unique=True, nullable=False)
    password = db.Column(db.String(100), nullable=False)
    name = db.Column(db.String(1000), nullable=False)


# Line below only required once, when creating DB.
# db.create_all()


# ---------- 03. load user ,AUTO check for current user ---------- #

@login_manager.user_loader
def load_user(user_id):
    # if user not valid, return None
    return User.query.get(user_id)


# ---------- 04. check current user's status ---------- #

@app.route('/')
def home():
    # if not logged in, current user = <User 3>
    # instead, current user = <flask_login.mixins.AnonymousUserMixin object at % Space %>
    return render_template("index.html", logged_in=current_user.is_authenticated)


@app.route('/register', methods=["GET", "POST"])
def register():
    if request.method == "POST":
        email = request.form['email']

        if User.query.filter_by(email=email).first() is not None:

            # ---------- 05. Flag: show a msg at once ---------- #

            flash("You've already sign up with the Email. Please log in instead.")
            return render_template("register.html")

        # ---------- 06. Werkzeug.security: hashing & salting ---------- #

        hash_and_salted_psw = generate_password_hash(
            request.form['password'],
            method='pbkdf2:sha256',
            salt_length=8
        )

        new_user = User(
            email=email,
            password=hash_and_salted_psw,
            name=request.form['name']

        )
        db.session.add(new_user)
        db.session.commit()

        # ---------- 07. Log in ---------- #

        login_user(new_user)

        return render_template("secrets.html", name=request.form['name'])
    return render_template("register.html")


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        email = request.form['email']
        password = request.form['password']
        user = User.query.filter_by(email=email).first()

        if user is not None:

            # ---------- 08. Werkzeug.security: check password ---------- #

            if check_password_hash(user.password, password):
                login_user(user, remember=True)

                # ---------- 09. Different from redirect & render_template ---------- #
                # redirect: switch to other pages (function)
                # render_template: show the 'html' with in the same url

                return redirect(url_for('secrets'))
            else:
                flash('Password incorrect. Please try again.')
                return render_template("login.html")
        else:
            flash('User is not exist. Please try again.')
            return render_template("login.html")

    return render_template("login.html")


# ---------- 10. Login required ---------- #

@app.route('/secrets')
@login_required
def secrets():
    return render_template("secrets.html", name=current_user.name,
                           logged_in=current_user.is_authenticated)


@app.route('/logout')
@login_required
def logout():
    # ---------- 11. Log out ---------- #

    logout_user()
    return redirect(url_for('home'))


@app.route('/download')
@login_required
def download():
    filename = request.args.get('filename')
    return send_from_directory('static/files/', filename, as_attachment=True)


if __name__ == "__main__":
    app.run(debug=True)

index.html

{% extends "base.html" %}
{{ super() }}
{% block content %}

<div class="box">
    <h1>Flask Authentication</h1>

    <!-- 04. check current user's status -->

    {% if not logged_in: %}

    <a href="{{ url_for('login') }}" class="btn btn-primary btn-block btn-large">Login</a>
    <a href="{{ url_for('register') }}" class="btn btn-secondary btn-block btn-large">Register</a>

    {% endif %}

</div>

{% endblock %}

login.html

{% extends "base.html" %}
{% block content %}

<div class="box">
    <h1>Login</h1>

    <form action="{{ url_for('login') }}" method="post">

<!-- 05. Flag: show a msg at once -->

        {% with messages = get_flashed_messages() %}
        {% if messages %}

            {% for message in messages %}
            <p style="color:red;">{{ message }}</p>
            {% endfor %}

        {% endif %}
        {% endwith %}

        <input type="text" name="email" placeholder="Email" required="required"/>
        <input type="password" name="password" placeholder="Password" required="required"/>
        <button type="submit" class="btn btn-primary btn-block btn-large">Let me in.</button>
    </form>
</div>

{% endblock %}

完整語法詳見GitHub


#Python #課堂筆記 #100 Days of Code







Related Posts

利用 dotenv 套件,設置在 Node.js 裡面的環境變數

利用 dotenv 套件,設置在 Node.js 裡面的環境變數

Regular Expression 正規表達式 快速上手

Regular Expression 正規表達式 快速上手

VS code 段落過長怎麼辦?

VS code 段落過長怎麼辦?


Comments